﻿using System;
using System.Web.Helpers;
using System.Web.Security;
using User.EF.Contracts;
using User.EF.Model;
using User.Global.Constvalues;
using User.Global.Contracts.Membership;
using User.Global.Contracts.Message;
using User.Global.Resources;

namespace User.Web.Common.Membership
{
    /// <summary>
    /// Einfache Userverwaltung für Login, Logout, PW vergessen, ....
    /// 
    /// Für die Umsetzung einer CustimIdentity ist folgender Artikel sehr hilfreich (ist hier NICHT implementiert!)
    /// http://stackoverflow.com/questions/1064271/asp-net-mvc-set-custom-iidentity-or-iprincipal
    /// </summary>
    public class DiMembership : IDiMembership<Member>
    {
        #region Member
        /// <summary>
        /// die aktuellen Queries
        /// </summary>
        private readonly IWebQuerySummary _queries;

        /// <summary>
        /// Allgemeine Messageverwaltung für die jeweiligen Meldungen die ausgegeben werden sollen.
        /// Hier einfach die Meldungen "hinzufügen" die angezeigt werden sollen.
        /// </summary>
        public IAddMessage Message { get; set; }
        #endregion

        #region Konstruktor
        /// <summary>
        /// Initialize
        /// </summary>
        /// <param name="queries">die aktuellen Membership Queries übergeben</param>
        public DiMembership(IWebQuerySummary queries)
        {
            if (queries == null)
            {
                throw new NullReferenceException("MemberShipQueries are Null");
            }

            _queries = queries;
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Userlogin mit den übergebenen Userdaten
        /// </summary>
        /// <param name="username">Username für den Login</param>
        /// <param name="password">Userpasswort für den Login</param>
        /// <param name="rememberMe">Soll der Login "gemerkt" werden</param>
        /// <returns>TRUE->Login erfolgreich | FALSE->Fehler beim Login</returns>
        public bool Login(string username, string password, bool rememberMe = false)
        {
            //Den User aus der DB abrufen
            Member user = _queries.MembershipQueries.GetMemberByUsername(username);
            if (user != null)
            {
                //Prüfen ob der Benutzer aktiviert ist.
                if (!user.IsActive)
                {
                    Message.Add(DefaultMessages.MemberDeactivatedLogginFailed, MessageTypes.warning);
                    return false;
                }

                //Erst prüfen ob der Account evtl. gesperrt wurde durch zu viele Fehllogins
                if (user.LoginTries > ConstLengthValues.MaxLoginTries)
                {
                    //Nach 10 Minuten den Account wieder freigeben, so lange einen Fehler zurückgeben, das man sich nich einloggen kann.
                    if (DateTime.Now < user.LastLoginTry.AddMinutes(ConstLengthValues.LockTimeInMinutes))
                    {
                        Message.Add(string.Format(ErrorMessages.AccountLocked, user.LastLoginTry.AddMinutes(ConstLengthValues.LockTimeInMinutes)), MessageTypes.error);
                        return false;
                    }
                }

                //das übergebene Passwort mit dem PW aus der DB vergleichen
                if (Crypto.VerifyHashedPassword(user.Password, string.Format("{0}{1}", password, user.PasswordSalt)))
                {
                    //Die FormsAuthentication nutzen um den User zu bestätigen.
                    FormsAuthentication.SetAuthCookie(user.Id.ToString(), rememberMe);
                    //Logintries zurücksetzten.
                    user.LoginTries = 0;
                    user.LastLoginTry = DateTime.Now;
                    _queries.Save();
                    return true;
                }
                else
                {
                    //Wenn das Passwort das falsche war, dann die Loginversuche hochzählen.
                    user.LoginTries++;
                    user.LastLoginTry = DateTime.Now;
                    _queries.Save();
                }
            }

            //Wenn etwas schief gelaufen ist, dann war das PW falsch oder der User existierte nicht.
            Message.Add(ErrorMessages.UsernameOrPasswordWrong, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Loggt den aktuellen User aus.
        /// </summary>
        public void Logout()
        {
            //Formsauthentication verwenden und den User entsprechend ausloggen
            FormsAuthentication.SignOut();
        }

        /// <summary>
        /// Erstellt den übergebenen User in der DB mit PW und Username
        /// </summary>
        /// <param name="member">Der User der angelegt werden soll</param>
        /// <param name="password">das Passwort welches gesetzt werden soll</param>
        /// <param name="username">der Username der gesetzt werden soll</param>
        /// <returns>TRUE->User wurde erfolgreich angelegt | FALSE->Fehler beim Anlegen des Users</returns>
        public bool CreateUser(string username, string password, Member member)
        {
            if (member == null || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
            {
                throw new NullReferenceException("Member, password or username is null");
            }

            //Prüfen ob der User mit dem Namen bereits existiert.
            if (!Exists(username))
            {
                member.InializeCurrentMember();
                //Das Passwort und die jeweiligen anderen MemberOptionen entsprechend setzten.
                member.Password = Crypto.HashPassword(string.Format("{0}{1}", password, member.PasswordSalt));
                member.Username = username;

                //Den User zur UserListe hinzufügen
                if (_queries.MembershipQueries.AddMember(member))
                {
                    //Speichern des Users in der DB
                    if (_queries.Save())
                    {
                        //Dem jeweiligen User noch die passenden Rechte zuweisen.
                        _queries.RightQueries.AddDefaultVocabelRights(member.Id);
                        Message.Add(string.Format(DefaultMessages.UserCreatedSuccessFully, username), MessageTypes.ok);
                        //Wenn der User erfolgreich angelegt wurde, direkt einloggen.
                        return Login(username, password);
                    }
                    else
                    {
                        Message.Add(ErrorMessages.UserCreationError, MessageTypes.error);
                    }
                }
                else
                {
                    //UserId war größer 0
                    Message.Add(ErrorMessages.UserExists, MessageTypes.error);
                }
            }
            else
            {
                Message.Add(ErrorMessages.UserExists, MessageTypes.error);
            }

            return false;
        }

        /// <summary>
        /// Prüft ob der übergebene Username beireits im System vorhanden ist.
        /// </summary>
        /// <param name="userName">Der Username der überprüft werden soll</param>
        /// <returns>TRUE->Bereits vorhanden | FALSE->User existiert noch nicht</returns>
        public bool Exists(string userName)
        {
            //Prüft ob der User bereits existiert.
            if (_queries.MembershipQueries.GetMemberByUsername(userName) != null)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Ändert das Passwort des Users
        /// </summary>
        /// <param name="userId">Die UserId für den das PW geändert werden soll</param>
        /// <param name="oldPassword">Das alte Passwort welches stimmen muss</param>
        /// <param name="newPassword">Das neue Passwort welches gesetzt werden soll</param>
        /// <returns>TRUE->Passwort setzten war erfolgreich | FALSE->Fehler beim setzten des neuen Passworts</returns>
        public bool ChangePassword(long userId, string oldPassword, string newPassword)
        {
            Member user = _queries.MembershipQueries.GetMemberById(userId);
            if (user != null)
            {
                //Erst prüfen ob das alte Passwort mit dem übergebenen alten passwort übereinstimmt.
                if (Crypto.VerifyHashedPassword(user.Password, string.Format("{0}{1}", oldPassword, user.PasswordSalt)))
                {
                   return SetUserPassword(user, newPassword);
                }
                else
                {
                    Message.Add(ErrorMessages.WrongPassword, MessageTypes.error);
                    return false;
                }
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Setzten des Passworts für den übergebenen User, hier wird nur das neue PW abgefragt.
        /// </summary>
        /// <param name="username">Der Username für den das PW geändert werden soll</param>
        /// <param name="newPassword">Das neue Passwort welches gesetzt werden soll</param>
        public bool ChangePassword(string username, string newPassword)
        {
            Member user = _queries.MembershipQueries.GetMemberByUsername(username);
            if (user != null)
            {
               return SetUserPassword(user, newPassword);
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Setzten des Passworts für den übergebenen User, hier wird nur das neue PW abgefragt.
        /// </summary>
        /// <param name="id">Die User-Id für die das PW geändert werden soll</param>
        /// <param name="newPassword">Das neue Passwort welches gesetzt werden soll</param>
        public bool ChangePassword(long id, string newPassword)
        {
            Member user = _queries.MembershipQueries.GetMemberById(id);
            if (user != null)
            {
                return SetUserPassword(user, newPassword);
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Erstellt ein Reset PW Token,  was z.B. per Mail versendet wird.
        /// </summary>
        /// <param name="username">Der Username für den das ResetToken  ist.</param>
        /// <param name="tokenExpirationInMinutesFromNow">Die Ablaufzeit in Minuten für das PW Token</param>
        /// <returns>Resetstring in dem der Username und auch die Ablaufzeit mit "enthalten sind"</returns>
        public string GeneratePasswordResetToken(string username, int tokenExpirationInMinutesFromNow = 1440)
        {
            string resetToken = string.Empty;

            if (Exists(username))
            {
                var member = _queries.MembershipQueries.GetMemberByUsername(username);
                resetToken = Crypto.GenerateSalt();
                member.PasswordVerificationToken = resetToken;
                //Das Token ist nur 24 Stunden gültig
                member.PasswordVerifivationTokenExpirationDate = DateTime.Now.AddMinutes(tokenExpirationInMinutesFromNow);
                //User speichern
                _queries.Save();
            }

            //Hier gibt es keine Fehlermeldung, wenn der Acc nicht existiert aus Sicherheitsgründen.
            Message.Add(ErrorMessages.PwResetWasSendToUser, MessageTypes.ok);
            return resetToken;
        }

        /// <summary>
        /// Setzt das PW anhand des übergebenen Tokens zurück
        /// </summary>
        /// <param name="passwordResetToken">Das ResetPW Token für den User</param>
        /// <param name="newPassword">Das neue PW welches gesetzt werden soll</param>
        /// <returns>TRUE->PW erfolgreich gesetzt  | FASLE->Fehler beim Setzten des PW</returns>
        public bool ResetPassword(string passwordResetToken, string newPassword)
        {
            if (!string.IsNullOrEmpty(passwordResetToken))
            {
                //Den User ermitteln anhand der Tokens.
                Member user = _queries.MembershipQueries.GetMemberByResetToken(passwordResetToken);
                if (user != null)
                {
                    //Die Passwortdaten entsprechend setzten.
                    return SetUserPassword(user, newPassword);
                }    
            }

            Message.Add(ErrorMessages.PwResetTokenNotValide, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Reaktivieren eines gesperrten Accounts, wenn dieser durch zu viele Fehllogins gesperrt wurde
        /// </summary>
        /// <param name="username">Der Username der wieder aktiviert werden soll</param>
        /// <returns>TRUE->Reaktivierung erfolgreich | FALSE->Fehler beim Reaktivieren des Accounts</returns>
        public bool ActivateLockedAccount(string username)
        {
            Member user = _queries.MembershipQueries.GetMemberByUsername(username);
            if (user != null)
            {
                user.LoginTries = 0;
                user.LastLoginTry = DateTime.Now;
                _queries.Save();
                return true;
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Aktivieren eines Accounts der Deaktiviert wurde.
        /// </summary>
        /// <param name="userId">Die UserId die aktiviert werden soll</param>
        public bool ActivateAccount(long userId)
        {
            Member member = _queries.MembershipQueries.GetMemberById(userId);
            if (member != null)
            {
                member.IsActive = true;
                _queries.Save();
                Message.Add(string.Format(DefaultMessages.MemberActivated, member.Username), MessageTypes.ok);
                return true;
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }

        /// <summary>
        /// Deaktivieren eines Accounts - Dieser Account kann sich nicht mehr einloggen.
        /// </summary>
        /// <param name="userId">Die UserId die deaktiviert werden soll</param>
        public bool DeactivateAccount(long userId)
        {
            Member member = _queries.MembershipQueries.GetMemberById(userId);
            if (member != null)
            {
                member.IsActive = false;
                _queries.Save();
                Message.Add(string.Format(DefaultMessages.MemberDeactivated, member.Username), MessageTypes.ok);
                return true;
            }

            Message.Add(ErrorMessages.UserDontExists, MessageTypes.error);
            return false;
        }
        #endregion

        #region private functions
        /// <summary>
        /// Setzten und speichern des Passworts für den User.
        /// </summary>
        /// <param name="user">der User für den das PW gesetzt werden soll</param>
        /// <param name="password">das Passwort welches gesetzt werden soll</param>
        private bool SetUserPassword(Member user, string password)
        {
            //Wenn für einen User ein neues PW gesetzt wird, dann werden die folgendne Schritte benötigt.
            user.PasswordVerificationToken = string.Empty;
            user.PasswordSalt = Crypto.GenerateSalt();
            user.Password = Crypto.HashPassword(string.Format("{0}{1}", password, user.PasswordSalt));
            user.PasswordCheangedDate = DateTime.Now;
            user.LoginTries = 0;
            return  _queries.Save();
        }
        #endregion
    }
}
